package de.medieninf.sensimg;

import java.io.IOException;

import javax.microedition.io.Connector;
import javax.microedition.io.Datagram;
import javax.microedition.io.DatagramConnection;

/**
 * Sends acceleration plus key data to a server.
 * @author pb
 */
public class UDPSender implements AccelerationListener {
    /**
     * Local cache of reference to settings.
     */
    private Settings settings = Settings.getSettings();
    /**
     * Local cache of reference to sensorService.
     */
    private SensorService sensorService = SensorService.getSensorService();
    /**
     * Open datagram connection.
     */
    private volatile DatagramConnection datagramConnection = null;
    /**
     * Local copy of the URL to send to.
     */
    private volatile String url;
    /**
     * Controlling whether we are actually sending/should send.
     */
    private volatile boolean serving = false;
    /**
     * ID incremented for each packet.
     */
    private int id = 1;
    /**
     * Local cache to reference to keyboardState.
     */
    private KeyboardState keyboardState;
    /**
     * Be informed if settings change.
     */
    private Runnable updater = new Runnable() {
        public void run() {
            Logger.log("updating UDPSender");
            boolean send = settings.getSend();
            if (serving == send) { // no change
                return;
            }
            serving = send;
            closeConnection();
            sensorService.unregister(UDPSender.this);
            if (serving) {
                initConnection();
                sensorService.register(UDPSender.this);
            }
        }
    };
    /**
     * Construct instance.
     * @param pkeyboardState giving global keyboardState
     */
    public UDPSender(final KeyboardState pkeyboardState) {
        this.keyboardState = pkeyboardState;
        settings.registerUpdate(updater);
    }

    /**
     * Make a JSON-String, not using a template.
     * @param x double value
     * @param y double value
     * @param z double value
     * @param keys String pressed keys
     * @param pid int of packet
     * @return String JSON
     */
    private String makeJSONString(final double x, final double y,
            final double z, final String keys, final int pid) {
        StringBuffer sb = new StringBuffer();
        sb.append("{");
        sb.append("\"x\": ");
        sb.append(Double.toString(x));
        sb.append(", ");
        sb.append("\"y\": ");
        sb.append(Double.toString(y));
        sb.append(", ");
        sb.append("\"z\": ");
        sb.append(Double.toString(z));
        sb.append(", ");
        sb.append("\"k\": \"");
        sb.append(keys);
        sb.append("\", ");
        sb.append("\"id\": ");
        sb.append(pid);
        sb.append("}");
        return sb.toString();
    }
    /**
     * create a datagram connection.
     */
    private void initConnection() {
        url = "datagram://" + settings.getCurrentSendUrl();
        try {
            datagramConnection = (DatagramConnection) Connector.open(url);
            Logger.log("initialized UDP connection " + url);
        } catch (IOException e) {
            String msg = "initializing datagram Connection failed";
            Logger.log(msg + e.getMessage());
            if (datagramConnection != null) {
                try {
                    datagramConnection.close();
                } catch (Exception f) {
                    msg = "closing on failed open failed";
                    Logger.log(msg + f.getMessage());
                }
            }
            datagramConnection = null;
        }
    }
    /**
     * Actually send the string message.
     * @param msg String the message
     * @return true iff was successfully sent
     */
    private boolean sendMessage(final String msg) {
        if (datagramConnection == null) {
            Logger.log("sendMessage altough datagramConnection is null");
            return false;
        }
        byte[] data = msg.getBytes();
        Datagram datagram;
        try {
            datagram = datagramConnection.newDatagram(data, data.length, url);
            datagramConnection.send(datagram);
            return true;
        } catch (IOException e) {
            Logger.log("sending message failed " + e.getMessage());
            return false;
        }
    }
    /**
     * Close the open datagram connection.
     */
    private void closeConnection() {
        if (datagramConnection != null) {
            try {
                datagramConnection.close();
            } catch (Exception e) {
                String msg = "failed to close connection ";
                Logger.log(msg + e.getMessage());
            }
        }
    }
    /**
     * Send the string down as datagram.
     * @param s String
     * @return true iff sending was successful.
     */
    private boolean sendString(final String s) {
        if (!serving) {
            return false;
        }
        if (datagramConnection == null) {
            initConnection();
            if (datagramConnection == null) {
                return false;
            }
        }
        return sendMessage(s);
    }
    /**
     * Called on acceleration data change.
     * @param x double
     * @param y double
     * @param z double
     */
    public final void update(final double x, final double y,
             final double z) {
        int localId = id++;
        String packet = makeJSONString(x, y, z,
                                       keyboardState.getKeys(),
                                       localId);
        if (!sendString(packet)) {
            Logger.log("sending of packet failed: " + packet);
        }
    }

    /**
     * Request to close sending.
     */
    public final void close() {
        serving = false;
        closeConnection();
    }
}
